package qj.tool.gae.dump;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.GZIPInputStream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONSerializer;
import qj.tool.gae.DB;
import qj.tool.gae.db.AEDB;
import qj.tool.gae.db.AEDB.GaeDataConverter;
import qj.tool.gae.db.JsonUtil;
import qj.util.Cols;
import qj.util.HttpUtil;
import qj.util.IOUtil;
import qj.util.ReflectUtil;

import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

public class DumpGaeS {

	public static void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		if (!"AAhhaa".equals(req.getHeader("Client"))) {
			return;
		}
		
		String action = req.getParameter("action");
		
		if ("plan".equals(action)) {
			plan(req, resp);
		} else if ("blob".equals(action)) {
			blob(req, resp);
		} else if ("startUpload".equals(action)) {
			startUpload(req, resp);
		}
	}

	public static Map<String, Profile> profiles;
	private static void plan(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		String profile = req.getParameter("profile");
		
		String plan = plan(profile);
		if (plan != null) {
			resp.getWriter().write(plan);
		}
	}

	public static void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		if (!"AAhhaa".equals(req.getHeader("Client"))) {
			return;
		}
		
		String queryString = req.getQueryString();
		String action = HttpUtil.getQueryParameter("action", queryString);
		
		if ("json".equals(action)) {
			json(req, resp);
		} else if ("uploadJson".equals(action)) {
			uploadJson(req, resp);
		} else if ("uploadBlob".equals(action)) {
			uploadBlob(req, resp);
		}
	}

	private static void startUpload(HttpServletRequest req, HttpServletResponse resp) {

		Profile profile = profiles.get(req.getParameter("profile"));
		if (profile==null) {
			return;
		}
		
		for (Class<?> class1 : profile.classes) {
//			System.out.println("Start Upload - Deleting " + class1);
			DB.deleteAll(class1);
		}
	}

	private static void uploadJson(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		String type = HttpUtil.getQueryParameter("type", req.getQueryString());
		Class<?> clazz = ReflectUtil.forName(type);
		List<?> objects = JsonUtil.toList(IOUtil.inputStreamToString(new GZIPInputStream(req.getInputStream()), "UTF-8"), clazz);
		DB.saveAll(objects);
	}
	
	private static void uploadBlob(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		String type = HttpUtil.getQueryParameter("type", req.getQueryString());
		String field = HttpUtil.getQueryParameter("field", req.getQueryString());
		String id = HttpUtil.getQueryParameter("id", req.getQueryString());
		Class<?> clazz = ReflectUtil.forName(type);
		
		GaeDataConverter<?> gaeConverter = DB.gaeConverter(clazz);

		Object object;
		if (gaeConverter.isId()) {
			object = DB.get(clazz, Long.parseLong(id));
		} else if (gaeConverter.isNamedId()) {
			object = DB.getName(id, clazz);
		} else {
			object = DB.get(AEDB.stringAsciiToKey(id), clazz);
		}
		
		ReflectUtil.setFieldValue(IOUtil.readData(req.getInputStream()), gaeConverter.fields().get(field), object);
		
		DB.save(object);
	}

	@SuppressWarnings("unchecked")
	private static void json(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		Class<?> entClass = ReflectUtil.forName(req.getParameter("type"));
		
		GaeDataConverter<?> gaeConverter = DB.gaeConverter(entClass);
		
		if (gaeConverter.isId()) {
			List<Long> ids = JsonUtil.toLongs(IOUtil.inputStreamToString_force(req.getInputStream()));
			resp.getWriter().write(JsonUtil.serializeList(DB.getByIds(entClass, ids)));
		} else if (gaeConverter.isNamedId()) {
			List<String> names = (List<String>) JSONSerializer.toJSON(IOUtil.inputStreamToString_force(req.getInputStream()));
			resp.getWriter().write(JsonUtil.serializeList(DB.getByNames(entClass, names)));
		} else {
			List<String> keys = (List<String>) JSONSerializer.toJSON(IOUtil.toString(req.getInputStream()));
			resp.getWriter().write(JsonUtil.serializeList(DB.getByKeys(entClass, Cols.yield(keys, AEDB.stringToKeyF ))));
		}
	}

	private static void blob(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		String id = req.getParameter("id");
		String field = req.getParameter("field");
		Class<?> entClass = ReflectUtil.forName(req.getParameter("type"));
		GaeDataConverter<?> gaeConverter = DB.gaeConverter(entClass);

		Key key = 
				gaeConverter.isId() ? KeyFactory.createKey(DB.entityType(entClass), Long.parseLong(id)) :
				(gaeConverter.isNamedId() ? KeyFactory.createKey(DB.entityType(entClass), id)
				: AEDB.stringAsciiToKey(id));
		try {
			Entity entity = DB.ds.get(key);
			Blob blob = (Blob) entity.getProperty(field);
			if (blob != null) {
				resp.getOutputStream().write(blob.getBytes());
			} else {
				resp.sendError(404);
			}
		} catch (EntityNotFoundException e) {
			throw new RuntimeException(e);
		}
	}
	
	public static class Profile {
		List<Class<?>> classes;

		public Profile(List<Class<?>> classes) {
			this.classes = classes;
		}
	}
	
	public static Profile profile(List<Class<?>> classes) {
		return new Profile(classes);
	}
	
	public static String plan(String profile) {

		Profile profileO = profiles.get(profile);
		if (profileO==null) {
			return null;
		}
		
		LinkedList<HashMap<String, Object>> plan = new LinkedList<HashMap<String, Object>>();
		for (Class<?> entClass : profileO.classes) {
			HashMap<String, Object> entPlan = new HashMap<String, Object>();

			entPlan.put("name", entClass.getName());

			GaeDataConverter<?> analyze = DB.gaeConverter(entClass);
			entPlan.put("keytype", analyze.isId() ? "long" : (analyze.isNamedId() ? "name" : "key"));
			
			LinkedList<String> blobFields = new LinkedList<String>();
			for (Entry<String, Field> entry : analyze.fields().entrySet()) {
				if (entry.getValue().getType().equals(byte[].class)) {
					blobFields.add(entry.getKey());
				}
			}
			entPlan.put("blobFields", blobFields);
			
			if (analyze.isId()) {
				entPlan.put("ids", DB.getLongIds(DB.query(entClass)));
			} else if (analyze.isNamedId()) {
				entPlan.put("ids", DB.getNames(DB.query(entClass)));
			} else {
				entPlan.put("ids", Cols.yield(DB.getKeys(DB.query(entClass)), AEDB.keyToStringF));
			}
			
			plan.add(entPlan);
		}
		
		return JSONSerializer.toJSON(plan).toString();
	}
}
